package redis

import (
	"context"
	"crypto/tls"
	"fmt"
	goredis "github.com/redis/go-redis/v9"
	"net"
	"strings"
	"sync"
	"time"
	"wzgames/pkg/config"
)

var (
	redisM *redisMgr
	ronce  sync.Once
)

type redisMgr struct {
	sync.RWMutex
	clients map[string]*RedisClient
}

type RedisClient struct {
	mode   RedisMode
	client goredis.UniversalClient
	opts   *goredis.UniversalOptions
}

func NewRedisClient(name string, confData config.ConfData) (*RedisClient, error) {
	if redisM == nil {
		ronce.Do(func() {
			redisM = &redisMgr{
				clients: make(map[string]*RedisClient),
			}
		})
	}

	return redisM.createClient(name, confData)
}

func (rdm *redisMgr) createClient(name string, confData config.ConfData) (*RedisClient, error) {
	if confData == nil {
		return nil, fmt.Errorf("redis New Client=%s create Error on nil configor", name)
	}
	c := rdm.parseConfig(confData)
	rdm.Lock()
	defer rdm.Unlock()
	if ex, ok := rdm.clients[name]; ok { // name exists
		return ex.UpdateConfig(name, confData)
	}
	client := &RedisClient{
		client: goredis.NewUniversalClient(c),
		opts:   c,
	}
	rdm.clients[name] = client
	return client, nil
}

func (rdm *redisMgr) Get(name string) *RedisClient {
	rdm.RLock()
	defer rdm.RUnlock()
	if p, ok := rdm.clients[name]; !ok {
		return nil
	} else {
		return p
	}
}

func (rdm *redisMgr) Destroy() {
	rdm.RLock()
	defer rdm.RUnlock()
	for _, v := range rdm.clients {
		v.Destroy()
	}
}

func (rdm *redisMgr) parseConfig(confData config.ConfData) *goredis.UniversalOptions {
	scheme := confData.GetString("scheme", "redis")
	opt := &goredis.UniversalOptions{
		Addrs:           strings.Split(confData.GetString("addrs", "127.0.0.1:6379"), ","),
		DB:              confData.GetInt("db", 0),
		Username:        confData.GetString("username", ""),
		Password:        confData.GetString("password", ""),
		MaxRetries:      confData.GetInt("maxRetries", 1),
		DialTimeout:     time.Duration(confData.GetInt("dialTimeout", 5)) * time.Second,
		ReadTimeout:     time.Duration(confData.GetInt("readTimeout", 5)) * time.Second,
		WriteTimeout:    time.Duration(confData.GetInt("writeTimeout", 5)) * time.Second,
		MinIdleConns:    confData.GetInt("minIdleConns", 10),
		MaxIdleConns:    confData.GetInt("maxIdleConns", 100),
		ConnMaxIdleTime: time.Duration(confData.GetInt("connMaxIdleTime", 5)) * time.Minute,
	}
	if scheme == "rediss" {
		h, _, err := net.SplitHostPort(opt.Addrs[0])
		if err != nil || h == "" {
			return opt
		}
		opt.TLSConfig = &tls.Config{
			ServerName: h,
			MinVersion: tls.VersionTLS12,
		}
	}
	return opt
}

func (rdc *RedisClient) UpdateConfig(name string, confData config.ConfData) (*RedisClient, error) {
	rdc.opts.Addrs = strings.Split(confData.GetString("addrs", "127.0.0.1:6379"), ",")
	rdc.opts.DB = confData.GetInt("db", 0)
	rdc.opts.Username = confData.GetString("username", "")
	rdc.opts.Password = confData.GetString("password", "")
	rdc.opts.MaxRetries = confData.GetInt("maxRetries", rdc.opts.MaxRetries)
	dialTimeout := confData.GetInt("dialTimeout", -100)
	if dialTimeout >= 0 {
		rdc.opts.DialTimeout = time.Duration(dialTimeout) * time.Second
	}
	readTimeout := confData.GetInt("readTimeout", -100)
	if readTimeout >= 0 {
		rdc.opts.ReadTimeout = time.Duration(readTimeout) * time.Second
	}
	writeTimeout := confData.GetInt("writeTimeout", -100)
	if writeTimeout >= 0 {
		rdc.opts.WriteTimeout = time.Duration(writeTimeout) * time.Second
	}
	rdc.opts.MinIdleConns = confData.GetInt("minIdleConns", rdc.opts.MinIdleConns)
	rdc.opts.MaxIdleConns = confData.GetInt("maxIdleConns", rdc.opts.MaxIdleConns)

	connMaxIdleTime := confData.GetInt("connMaxIdleTime", -100)
	if connMaxIdleTime >= 0 {
		rdc.opts.ConnMaxIdleTime = time.Duration(connMaxIdleTime) * time.Minute
	}

	scheme := confData.GetString("scheme", "redis")
	if scheme == "rediss" {
		rdc.opts.TLSConfig = &tls.Config{
			ServerName: confData.GetString("name", "redis-server"),
			MinVersion: tls.VersionTLS12,
		}
	} else {
		rdc.opts.TLSConfig = nil
	}

	return rdc, nil
}

func (rdc *RedisClient) Get() goredis.UniversalClient {
	return rdc.client
}

func (rdc *RedisClient) Destroy() error {
	return rdc.client.Close()
}

func (rdc *RedisClient) Subscribe(ctx context.Context, consume ConsumeFunc, channels ...string) error {
	psc := rdc.client.Subscribe(ctx, channels...)
	defer psc.Close()

	go func() {
		for {
			select {
			case <-ctx.Done():
			case msg := <-psc.Channel():
				err := consume(msg.Channel, msg.Pattern, msg.Payload)
				if err != nil {

				}
			}
		}
	}()

	return nil
}
